package com.swak.fx.support.ui.skin;

import javafx.beans.property.DoubleProperty;
import javafx.beans.property.ObjectProperty;
import javafx.css.PseudoClass;
import javafx.geometry.Bounds;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.control.skin.LabelSkin;

public abstract class BaseLabelSkin extends LabelSkin {

	private static final String DECORATION_STYLE_CLASS = "ui-decoration";
	private static final PseudoClass HAS_DECORATION_NODE = PseudoClass.getPseudoClass("decoration-node-visible"); //$NON-NLS-1$

	final Label control;
	private Node decorationNode;
	private Pos pos;

	protected abstract ObjectProperty<Pos> posProperty();

	protected abstract ObjectProperty<Node> decorationProperty();

	protected abstract DoubleProperty offsetXProperty();

	protected abstract DoubleProperty offsetYProperty();

	public BaseLabelSkin(Label control) {
		super(control);
		this.control = control;
	}

	protected void init() {
		this.updateDecoration();
		registerChangeListener(this.posProperty(), e -> updateDecorationOffset());
		registerChangeListener(this.decorationProperty(), e -> updateDecoration());
		registerChangeListener(this.offsetXProperty(), e -> updateDecorationOffset());
		registerChangeListener(this.offsetYProperty(), e -> updateDecorationOffset());
	}

	protected void updateDecoration() {
		this.pos = this.posProperty().get();
		getChildren().remove(this.decorationNode);
		Node decorationNode = this.decorationProperty().get();
		if (decorationNode != null) {
			this.decorationNode = decorationNode;
			this.decorationNode.setManaged(false);
			this.decorationNode.getStyleClass().add(DECORATION_STYLE_CLASS);
			getChildren().add(decorationNode);
			this.layoutGraphic();
		} else {
			this.decorationNode = null;
		}

		this.control.pseudoClassStateChanged(HAS_DECORATION_NODE, decorationNode != null);
	}

	protected void updateDecorationOffset() {
		if (this.decorationNode != null) {
			this.layoutGraphic();
		}
	}

	@Override
	protected void layoutChildren(double x, double y, double w, double h) {
		super.layoutChildren(x, y, w, h);
		this.updateDecorationOffset();
	}

	private void layoutGraphic() {
		// Because we made decorationNode unmanaged, we are responsible for sizing it:
		this.decorationNode.autosize();

		// Now get decorationNode's layout Bounds and use for its position computations:
		final Bounds decorationNodeLayoutBounds = this.decorationNode.getLayoutBounds();
		final double decorationNodeWidth = decorationNodeLayoutBounds.getWidth();
		final double decorationNodeHeight = decorationNodeLayoutBounds.getHeight();

		Bounds targetBounds = this.control.getLayoutBounds();
		double x = targetBounds.getMinX();
		double y = targetBounds.getMinY();

		double targetWidth = targetBounds.getWidth();
		if (targetWidth <= 0) {
			targetWidth = this.control.prefWidth(-1);
		}

		double targetHeight = targetBounds.getHeight();
		if (targetHeight <= 0) {
			targetHeight = this.control.prefHeight(-1);
		}

		// offset
		double offsetX = this.offsetXProperty().get();
		double offsetY = this.offsetYProperty().get();

		// x
		switch (pos.getHpos()) {
		case CENTER:
			x += targetWidth / 2 - decorationNodeWidth / 2.0;
			break;
		case LEFT:
			x -= decorationNodeWidth / 2.0;
			break;
		case RIGHT:
			x += targetWidth - decorationNodeWidth / 2.0;
			break;
		}

		// y
		switch (pos.getVpos()) {
		case CENTER:
			y += targetHeight / 2 - decorationNodeHeight / 2.0;
			break;
		case TOP:
			y -= decorationNodeHeight / 2.0;
			break;
		case BOTTOM:
			y += targetHeight - decorationNodeHeight / 2.0;
			break;
		case BASELINE:
			y += this.control.getBaselineOffset() - this.decorationNode.getBaselineOffset()
					- decorationNodeHeight / 2.0;
			break;
		}
		this.decorationNode.relocate(x + offsetX, y + offsetY);
	}

}
